<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - materials - depth peel</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #000;
				color: #fff;
				margin: 0px;
				overflow: hidden;
			}

			a {
				color: #ffa;
				font-weight: bold;
			}

			#info {
				color: #fff;
				position: absolute;
				top: 10px;
				width: 100%;
				text-align: center;
				z-index: 100;
				display:block;
			}
		</style>
	</head>

	<body>
		<div id="info">
			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl depth peel demo by <a href="http://dusanbosnjak.com" target="_blank" rel="noopener">pailhead</a>.<br />
		</div>

		<script src="./three.min.recent.js"></script>

		<script src="./OrbitControls.js"></script>

		<script src="./WebGL.js"></script>

		<script src="./dat.gui.min.js"></script>

		<script src="./stats.min.js"></script>

		<script>

			if ( WEBGL.isWebGLAvailable() === false ) {

				document.body.appendChild( WEBGL.getWebGLErrorMessage() );

			}

			var renderer, scene, camera;

			var	gui = new dat.GUI( { width: 350 } );

			var stats

			var MAX_LAYERS = 6

			var debugScene, debugCamera
			var depthMaterial, depthTarget, compositeMaterial, gl

			var compositeScene = new THREE.Scene()

			var transparentObjects = []
			var opaqueObjects = []
			var targets = []

			var options = {
				layers: 1,
				opacity: 0.5,
				doubleSide: false,
				enabled: false,
				monochrome: false,
			}

			var globalPeelUniforms = { 
				uLayer: { value: 0 },
				uPrevDepthTexture: { value: null },
				uPrevColorTexture: { value: null },
				uScreenSize: { value: new THREE.Vector2(1,1) },
				uDepthPeel: { value: Number(options.enabled) },
				uDepthOffset: { value: 0 },
			} 

			gui.add(options, 'layers', 1, 6).step(1)
			gui.add(options, 'opacity', 0, 1).onChange(onOpacityChange)
			gui.add(options, 'enabled').onChange(onModeChange)
			gui.add(options, 'doubleSide').onChange(onSideChange)
			gui.add(options, 'monochrome').onChange(onMonochromeChange)

			function onOpacityChange(val){
				transparentObjects.forEach(o=>o.material.opacity=val)
			}

			function getColorKey(){
				return options.monochrome ? 'colorB' : 'colorA'
			}

			function onMonochromeChange(){
				var color = getColorKey()
				transparentObjects.forEach(o=>{
					o.material.color.copy(o.material[color])// 这个操作有点迷啊
				})
			}

			function onSideChange(val){ 
				
				var side = getSide()

				depthMaterial.side = side
				depthMaterial.needsUpdate = true

				scene.traverse(o=>{
					if(o.material) {
						o.material.side = side
						o.material.needsUpdate = true
					}
				})
			}

			function onModeChange(){
				transparentObjects.forEach(o=>{
					o.material.transparent = !options.enabled
				})
				globalPeelUniforms.uDepthPeel.value = Number(options.enabled) 
			}

			function getSide(){
				return options.doubleSide ? THREE.DoubleSide : THREE.FrontSide 
			}

			function depthOnbeforeCompile(shader){
				// 这样就能使得每次更新时，都拿这个对象去更新吗
				shader.uniforms.uScreenSize = globalPeelUniforms.uScreenSize
				shader.uniforms.uPrevDepthTexture = globalPeelUniforms.uPrevDepthTexture
				shader.uniforms.uLayer = globalPeelUniforms.uLayer
				shader.uniforms.uDepthOffset = globalPeelUniforms.uDepthOffset
				shader.uniforms.uDepthPeel = globalPeelUniforms.uDepthPeel
				// 它在这里修改传入着色器的数据，我得换个地方
				shader.fragmentShader = `
					uniform vec2 uScreenSize;
					uniform sampler2D uPrevDepthTexture;
					uniform int uLayer;
					uniform int uDepthPeel;
					uniform float uDepthOffset;

					${shader.fragmentShader}
				`
				//peel depth 
				shader.fragmentShader = shader.fragmentShader.replace(
				//	/}$/gm, // 所有大括号的结束都来这么一段，我觉得只要在main函数开始那里来一段就可以了吧，如果不渲染，前面的运算都毫无意义吧
					/void main\(\)\{/,
					`	
					void main(){
						// if(uDepthPeel == 0) return;// 我改了之后这个就不合适了

						if(uDepthPeel == 0 && uLayer != 0 ){
						
						    vec2 screenPos = gl_FragCoord.xy * uScreenSize;            //这里怎么是乘,因为他传的数据是倒数
						    
						    float prevDepth = unpackRGBAToDepth(texture2D(uPrevDepthTexture,screenPos));
						    
						    if(prevDepth + uDepthOffset - gl_FragCoord.z >= 0. ){     // 本次渲染的片元如果比
						   	    discard;
						    }
						}
				    }
					`
				)

			}

			//premultiply alpha
			function colorOnBeforeCompile(shader){
				console.log('before');
			console.log(shader);
				shader.fragmentShader = shader.fragmentShader.replace('#include <packing>','')
				shader.fragmentShader = `
					#include <packing>
					uniform sampler2D uPrevColorTexture;

					${shader.fragmentShader}
				`
				//this has early return
				depthOnbeforeCompile(shader)

				shader.fragmentShader = shader.fragmentShader.replace(
					/}$/gm,
					`	
						gl_FragColor.xyz *= gl_FragColor.a;
					}
					`
				)
			}

			function initTargets(){
				if(targets.length)
					for( let i = 0 ; i < 4 ; i++ ){
						targets.pop().dispose()// 调用会清理掉对应的帧缓冲区
					}

				for( let i = 0 ; i < 4 ; i ++ ){

					targets.push(
						new THREE.WebGLRenderTarget(
							window.innerWidth,
							window.innerHeight, 
							{
								minFilter: THREE.NearestFilter, 
								magFilter: THREE.NearestFilter 
							}
						)
					)

					debugScene.children[i].material.map = targets[i]
				
				}

			}

			init();

			function init() {


				// renderer
				renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true});// 渲染器开启  缓冲区不会被清除，直到被覆盖或者被使用者清除
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.autoClear = false// 关了这个
				document.body.appendChild( renderer.domElement );

				stats = new Stats();
				document.body.appendChild( stats.dom )

				// renderer.gammaOutput = true;//现在只提供了一个factor 说向下兼容，到底兼容了什么 原来就是改名了
				renderer.outputEncoding = THREE.sRGBEncoding ;// 没用了，设置渲染目标后会自动改为纹理的编码方式

				// scene
				scene = new THREE.Scene();

				scene.autoUpdate = false;

				// camera
				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
				camera.position.set( - 10, 0, 33 );
				scene.add( camera );

				// controls
				var controls = new THREE.OrbitControls( camera, renderer.domElement );
				controls.addEventListener( 'change', render );
				controls.minDistance = 10;
				controls.maxDistance = 150;
				controls.screenSpacePanning = true

				// 难怪他要维护自己的transparent队列，因为根本没开，这样就全部是类似transmission了， 不过我还是要放到里面
				// ---------------------------DEPTH PEEL ------------------------------- //
				debugCamera = new THREE.PerspectiveCamera( 40 , window.innerWidth / window.innerHeight , 1, 10)
				debugCamera.position.z = 5
				debugScene = new THREE.Scene()
				const planeGeometry = new THREE.PlaneBufferGeometry(2,2,1,1)

				//debug
				for( let i = 0 ; i < 4 ; i ++ ){
					const plane = new THREE.Mesh(planeGeometry, new THREE.MeshBasicMaterial())
					plane.material.depthTest = false
					plane.scale.multiplyScalar(0.3)
					debugScene.add(plane)
					plane.position.x = (i - 2 + 0.5) * 0.7
					plane.position.y = 1.5
				}
				// 这个着色器负责把 这一次的和上次的混合 在一起 src是本色 权重1 dst是来源 权重是1 -src.a
				compositeMaterial = new THREE.ShaderMaterial({
					uniforms:{
						uTextureA: { value: null },
						uTextureB: { value: null },
						uBlit: {value: 0 }// 这个参数没用上
					},
					vertexShader:`
						varying vec2 vUv;
						void main(){
							vUv = uv;
							gl_Position = vec4(position.xy,0.,1.);
						}
					`,
					fragmentShader:`
						varying vec2 vUv;
						uniform sampler2D uTextureA;
						uniform sampler2D uTextureB;
						uniform int uBlit;

						void main(){
							vec4 src = texture2D(uTextureA,vUv);
							vec4 dst = texture2D(uTextureB,vUv);
	
							float a1 = 1.-src.a;					
							gl_FragColor.a = src.a + a1 * dst.a;
							gl_FragColor.rgb = src.rgb + a1 * dst.rgb;
							gl_FragColor.rgb /= gl_FragColor.a;
						}
					`,
					transparent:true,
					// depthTest:false,//深度写入关闭了，深度测试还有必要关吗
					depthWrite: false,
				})

				const compositePlane = new THREE.Mesh(planeGeometry,compositeMaterial)
				compositePlane.frustumCulled = false
				compositeScene.add(compositePlane)
				// 改造depth材质 用于绘制深度图
				depthMaterial = new THREE.MeshDepthMaterial()
				depthMaterial.side = getSide()// 忽略
				depthMaterial.depthPacking = THREE.RGBADepthPacking
				depthMaterial.onBeforeCompile = depthOnbeforeCompile

				initTargets()

				globalPeelUniforms.uScreenSize.value.set(1/window.innerWidth,1/window.innerHeight)
				const numItems = 10

				const boxGeometry = new THREE.BoxBufferGeometry(2,2,2,1,1,1)

				//setup objects  把这一部分放到 transparent队列里
				for ( let i = 0 ; i < numItems ; i ++ ){

					const mesh = new THREE.Mesh(
						boxGeometry, 
						new THREE.MeshPhongMaterial({
						// new THREE.MeshBasicMaterial({
							opacity: options.opacity,
							side: getSide(),
						})
					)

					mesh.renderOrder = 1

					transparentObjects.push(mesh)
					
					mesh.material.onBeforeCompile = colorOnBeforeCompile // 它这里是初始化，我应该

					mesh.material.colorA = new THREE.Color().setHSL(
						(i%2==0 ? numItems - i : i ) / numItems,
						1,
						0.5
					)

					mesh.material.colorB = new THREE.Color().setRGB(1,1,1)

					mesh.material.color.copy(mesh.material[getColorKey()])

					mesh.rotation.y = i * Math.PI/2
					mesh.scale.y = 2
					mesh.position.y = (i - numItems /2)* 2.5 
					mesh.scale.x = 10

					scene.add(mesh)
				}

				// const opaque = new THREE.MeshBasicMaterial({color:'white'})
				const opaque = new THREE.MeshPhongMaterial({color:'white'})
				// opaque.onBeforeCompile = colorOnBeforeCompile// 	其他物体也得这样  不应该啊

				for( let i = 0 ; i < 4 ; i ++ ){
					const m = new THREE.Mesh(boxGeometry, opaque)
					scene.add(m)
					const angle = i / 2 * Math.PI
					m.position.set(Math.sin(angle),0,Math.cos(angle)).multiplyScalar(10)
					m.scale.multiplyScalar(1.5)
					m.scale.y = 3.5
				}

				// ambient
				scene.add( new THREE.AmbientLight( 0xffffff, .5 ) );

				// light
				var light = new THREE.PointLight( 0xffffff, 0.8 );
				light.position.set(20,20,20)
				scene.add( light );

				window.addEventListener( 'resize', onWindowResize, false );

				scene.updateMatrixWorld()

				onModeChange()

				animate()

			}

			function renderDepthPeel(){

				//clear main frame
				renderer.setClearColor(0x000,1)
				renderer.clear()

				globalPeelUniforms.uLayer.value = 0

				//render first depth
				scene.overrideMaterial = depthMaterial// 深度材质会使得颜色存储深度，这样目标纹理上就能存储深度信息
				renderer.setClearColor(0xffffff,1)//  用白色清理，这样没有绘制的区域会认为深度极小，在深度比较的时候说明这部分不是要剥离的部分，就不会走剥离的逻辑
				renderer.clear()
				renderer.setRenderTarget(targets[0])
				renderer.render( scene, camera )

				//first color
				scene.overrideMaterial = null// 迫使场景里全部几何体都用这个材质
				renderer.setClearColor(0x000,0)// 这里应该用默认的背景色清理了吧
				renderer.setRenderTarget(targets[2])
				renderer.clear()
				renderer.render( scene, camera )



				for( let i = 0 ; i < options.layers ; i ++ ){
					
					const a = i % 3 //shift these around   上一次的深度信息 // 并且在更新到着色器之后，复用来绘制 组合后的图
					const b = (i+1) % 3					  //这次深度信息的绘制目标
					const c = (i+2) % 3					  // 上次剥离得到的结果绘制目标	
					const d = 3 //peel into this          这次剥离得到的图，剥离就是砍掉离相机最近的一部分顶点，后面的顶点就露出了

					globalPeelUniforms.uPrevDepthTexture.value = targets[a].texture  // 更新深度图  这个更新恐怕根本没用，变异钩子应该只会调用一次，所以我应该单独写一个材质吗，不然找到更新对象
					globalPeelUniforms.uLayer.value = i + 1

					//render next depth
					scene.overrideMaterial = depthMaterial
					renderer.setClearColor(0xffffff,1)
					renderer.clear()
					renderer.setRenderTarget(targets[b])
					renderer.render( scene, camera, )
					
					//peel 我需要一个跟trasmision 那样的纹理更新机制
					scene.overrideMaterial = null
					renderer.setClearColor(0x000,0)
					renderer.setRenderTarget(targets[d])
					renderer.clear()
					renderer.render( scene, camera, )

					//combine
					compositeMaterial.uniforms.uTextureA.value = targets[c].texture  // 更新上一张图  这次剥离的结果
					compositeMaterial.uniforms.uTextureB.value = targets[d].texture 
					
					// 之前的版本是  target  force clear
					renderer.setRenderTarget(targets[a]) // 复用a  
					 renderer.clear() //这里清理一下就没了， 但是按之前的代码这里确实应该清理
					renderer.render( compositeScene, camera)// 

				}
				// 循环结束开始绘制不透明物体，第一次只绘制不透明物体

				//render final result over opaque objects
				globalPeelUniforms.uPrevDepthTexture.value = null

				transparentObjects.forEach(o=>o.visible = false) // 也就是这里改变了透明队列的可见性，其他地方没用
				
				renderer.setRenderTarget(null) ; //  最后一帧要要把渲染目标还原
				// renderer.clear() // 这里就不该了清理了 按现在的逻辑 要避免清理，应置空background
				renderer.render( scene, camera )
				 renderer.render( compositeScene, camera )
				transparentObjects.forEach(o=>o.visible = true)
			}

			function onWindowResize() {

				renderer.setSize( window.innerWidth, window.innerHeight );

				globalPeelUniforms.uScreenSize.value.set(1/window.innerWidth,1/window.innerHeight)

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				debugCamera.aspect = camera.aspect
				debugCamera.updateProjectionMatrix()
				
				initTargets() // 渲染目标也需要重新初始化
			
			}

			function animate() {

				requestAnimationFrame( animate )

				stats.update()

				camera.updateMatrixWorld()

				render()

			}

			function render() {

				if(!options.enabled){

					renderer.clear()
					renderer.render( scene, camera )
				
				} else {
					renderer.clear()//这里不清理 透明物体会重 ，即便清理不透明物体还是会重
					renderDepthPeel()
					// renderer.render( debugScene , debugCamera )
				
				}

			}

		</script>

	</body>
</html>
